<!DOCTYPE html>
<html>
  <head>
    <title>
      Test AudioBufferSourceNode looping without explicit duration
    </title>
    <script src="../../resources/testharness.js"></script>
    <script src="../../resources/testharnessreport.js"></script>
    <script src="../resources/audit-util.js"></script>
    <script src="../resources/audit.js"></script>
  </head>
  <body>
    <script id="layout-test-code">
      // Reasonably low sample rate for the optimum test speed.
      let sampleRate = 8192;

      let audit = Audit.createTaskRunner();

      // Task: create a short linear ramp and enable looping.  The test will
      // verify that the ramp was looped the appropriate number of times.
      audit.define('loop-count', (task, should) => {
        // How many loops of the source we want to render. Any whole number
        // greater than 1 will work.
        let loopCount = 4;
        let sourceFrames = 8;
        let renderFrames = sourceFrames * loopCount;

        let context = new OfflineAudioContext(1, renderFrames, sampleRate);
        let source = context.createBufferSource();
        let linearRampBuffer = createLinearRampBuffer(context, sourceFrames);

        source.buffer = linearRampBuffer;
        source.connect(context.destination);

        // Enable looping and start the source with an offset, but without a
        // duration.  In this case, the source should loop "forever".
        // See crbug.com/457009.
        source.loop = true;
        source.start(0, 0);

        context.startRendering()
            .then(function(renderedBuffer) {
              let badIndex = -1;
              let success = true;

              let actual = renderedBuffer.getChannelData(0);
              let linearRamp = linearRampBuffer.getChannelData(0);

              // Manually create a |loopCount| copies of linear ramps.
              let expected = new Float32Array(linearRamp.length * loopCount);
              for (let i = 0; i < loopCount; i++)
                expected.set(linearRamp, linearRamp.length * i);

              // The actual output should match the created loop.
              should(actual, 'The output of actual and expected loops')
                  .beEqualToArray(expected);
            })
            .then(() => task.done());
      });

      // Task: Test that looping an AudioBufferSource works correctly if the
      // source is started and the buffer is assigned later, but before the
      // source would start.
      audit.define('delayed-start', (task, should) => {
        let renderDuration = 2;
        let context =
            new OfflineAudioContext(2, sampleRate * renderDuration, sampleRate);
        let linearRampBuffer = createLinearRampBuffer(context, 128);

        let normal = context.createBufferSource();
        let delayed = context.createBufferSource();
        let merger = context.createChannelMerger(2);

        // Connect the normally started source to the left channel, and the
        // delayed to the right channel.
        normal.connect(merger, 0, 0);
        delayed.connect(merger, 0, 1);
        merger.connect(context.destination);

        normal.buffer = linearRampBuffer;
        normal.loop = true;
        delayed.loop = true;

        normal.start(1, 0);
        delayed.start(1, 0);

        // Assign the buffer to the delayed source node at 0.5 second.
        context.suspend(0.5).then(function() {
          delayed.buffer = linearRampBuffer;
          context.resume();
        });

        context.startRendering()
            .then(function(buffer) {
              // The left and right channel must match regardless of the timing
              // of buffer assignment.
              should(
                  buffer.getChannelData(0),
                  'The content of the left and right channel')
                  .beEqualToArray(buffer.getChannelData(1));
            })
            .then(() => task.done());
      });

      audit.run();
    </script>
  </body>
</html>
